/*
 * controller.c
 *
 * This file contains the source code for the controller struct and related
 * functions.
 * The controller's purpose is to act as the central driver for events that take
 * place in the game itself, and structurally is a queue of functions that are
 * called in a first-in-first-out fashion when asked to by an outside general
 * controller (usually a UI of some kind).
 *
 * Insertion and pop execute in O(1) time.
 *
 * The controller is thread-safe. Multiple threads can add to the event-queue
 * simultaneously, although for thread-safety controller_add() and
 * controller_next() may block.
 */

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <utils/error.h>
#include "controller.h"
#include "event.h"

#define QUEUE_SIZE 20

/*
 * The global controller object.
 */
action * event_queue[QUEUE_SIZE];
unsigned int queue_top;
unsigned int queue_bottom;

/*
 * The controller mutex.
 * When this mutex is locked, adding to the controller blocks. Also, popping
 * events block when there is only 1 event in the queue.
 */
pthread_mutex_t controller_mutex;

/*
 * Flag determining whether or not the controller has been initialized.
 */
int cont_init = 0;

/*
 * Creates a new controller object.
 */
int controller_init() {
    if( cont_init ) {
        printerr( WARNING_CONT_REINIT );
        return 1;
    }
    else {
        queue_top = 0;
        queue_bottom = 0;
        pthread_mutex_init(&controller_mutex, NULL);
        cont_init = 1;
        return 0;
    }
}

/*
 * Adds the given function pointer to the controller's action queue.
 */
void controller_add( action *a ) {
    if( cont_init ) {
        pthread_mutex_lock(&controller_mutex);
        /* Check if we have room to add an action. */
        if( (queue_bottom + 1) % QUEUE_SIZE != queue_top ) {
            event_queue[queue_bottom] = a;
            queue_bottom++;
            if( queue_bottom >= QUEUE_SIZE ) {
                queue_bottom = 0;
            }
        }
        pthread_mutex_unlock(&controller_mutex);
    }
    else {
        printerr( WARNING_CONT_ADD );
    }
}

/*
 * Pops the next event off the queue and executes it.
 * If the queue is empty, controller_next() does nothing.
 */
int controller_next() {
    if( cont_init ) {
        /* Is there an action to handle? */
        if( queue_top != queue_bottom ) {
            handle_action(event_queue[queue_top]);
            queue_top++;
            if( queue_top >= QUEUE_SIZE ) {
                queue_top = 0;
            }
            return 1;
        }
        pthread_mutex_lock(&controller_mutex);
        /* We need to compare queue_top and queue_bottom twice because while we
           don't need to block adding actions if there's already actions that
           need to be handled, we do need to block when we're checking
           queue_bottom to see if we should restructure the queue. If we don't
           block on this second check there's a potential race condition. */
        if( queue_top == queue_bottom && queue_top != 0 ) {
            /* Updating the queue is easiest (read: fastest) when we don't have
               to worry about wrapping around. Therefor, if there's nothing to
               do anyway, we may as well set the queue top position to the top
               of the array. */
            queue_top = 0;
            queue_bottom = 0;
        }
        pthread_mutex_unlock(&controller_mutex);
        return 0;
    }
    else {
        printerr( WARNING_CONT_INIT );
        return 0;
    }
}

/*
 * Pops every event off the queue and executes it until the queue is empty.
 */
void controller_empty() {
    while( controller_next() );
}
